package com.cskaoyan.javse.oop2._1extends._7initialization._0introduction;

/**
 * 子类对象的初始化
 *
 * 我们先复习一下我们认识引用数据类型的过程：**
 * 1. 首先我们从数组对象的初始化开始，我们知道引用数据类型变量分为两个部分，在堆上创建对象，在栈中创建引用，引用通过存储地址指向堆上的对象。对象是引用数据类型的实质，但是我们不能直接访问对象，而是通过栈上的引用间接访问堆上的对象。
 * 2. 然后我们学习了对象与类的概念，引入了类加载的概念。并且使用数据类型的定义统一了引用数据类型和基本数据类型：
 *    数据类型：一组数据 + 基于数据的一组合法操作
 *    引用数据类型：成员变量的合集 + 成员方法的合集
 *    也就是说，引用数据类型（类）是特殊的数据类型，是自定义的数据类型。这种自定义的数据类型JVM不认识它，需要一个了解认识它的过程，称之为类加载。
 *    JVM通过类加载了解一个类型，从而能够完成创建对象，或者其它的一些操作。
 * 3. 现在我们学习了继承的概念，在继承中，子类会继承父类的成员，子类对象可以访问父类成员。那么问题就来了：
 *    父类的成员从何而来？父类的成员变量的赋值顺序是怎么样的？结果是怎么样的?等等
 *    以上问题就是子类对象的初始化问题，即研究子类对象创建时，父类的成员从何而来，相关成员变量的赋值等相关的问题。
 *
 * 我们通过一个引例，练习来讲解这个问题：
 *
 * > 1. 创建一个Person类，有name属性和eat方法
 * > 2. 创建一个Star类，有stageName属性和sing方法
 * > 3. Star类要继承Person类
 * > 4. 创建Star类对象，画出这个过程的内存图，描述子类对象初始化的过程。
 *
 * 在这个创建子类对象的过程中,首先仍然考虑类加载,但是情况有变,Star有父类,父类需要类加载吗?
 *  肯定需要,但问题是,类加载的顺序是什么样的?
 *  是先父后子的
 *
 * 于是我们现在，类加载的时机就变成了四种：
 *      1.new对象
 *      2.启动main方法
 *      3.访问静态成员
 *      4.子类触发类加载，优先类加载父类（不管子类是何种方式触发类加载）
 *
 * 在子类触发类加载，先类加载父类的过程中，先从顶层父类Object开始，从上到下，逐个完成类加载。子类是最后类加载完成的
 *
 * 1.创建子类对象，那么堆上究竟会存在几个对象？
 * 只会存在一个子类对象，不存在任何父类对象
 *
 * 2.父类的东西被继承了，现在只有一个子类对象，这些东西怎么存？
 * 接下来子类对象中会专门开辟一片独立的区域，用来存储父类的成员，而子类自身的成员仍会存储在自身对象当中。这样子类对象就被划分为了两个区域。
 *                 那片专属于父类成员的区域，可以近似地看成一个父类对象，实际使用下来，它也雀食完全可以当成一个父类对象来使用
 *                 这片区域用super关键字指向它
 *                 this表示当前对象，super就可以近似看成表示父类对象
 *
 * 3.创建对象完毕后，接下来就需要考虑成员变量地赋值问题：赋值的顺序是什么？结果是什么？
 *              首先说明，默认初始化的顺序无需考虑，只要对象结构存在，它们一定有默认值了，谁先谁后无所谓。
 *             除开默认初始化，赋值顺序是什么呢？
 * 先父后子的，父类成员变量先完成赋值，随后才是子类成员变量的赋值。而且继承如果有多层，那就肯定从顶层父类开始完成赋值。
 *
 * 以上，子类对象创建完毕。
 *
 * 父子类成员变量的赋值顺序的"先父后子"，是怎么保证的呢？为什么能够体现出这种特点呢？接下来，我们详细分析一下子类对象中，父子类成员变量的赋值顺序。
 *
 * 在一次对象的成员变量赋值中，构造器肯定是最后执行的。我们假如父类的构造器，先于子类构造器执行，那么父类的所有成员变量的赋值方式，都是先于子类。
 * new对象时，构造代码块会执行，我们发现父类的构造代码块在创建子类对象时，会先于子类构造代码块执行
 * 这说明，父类构造器雀食先于子类构造器执行。
 *
 * 为了确保父类构造器先于子类构造器执行，在Java当中，任何一个构造器，如果它的第一行代码，没有直接显式的去调用另一个构造器（this/super），那么第一行永远隐含一条语句：
 *      super();
 * 表示调用调用父类的无参构造方法
 *
 *这种我们无需写任何代码,就能保证子类对象初始化时,先父后子的赋值顺序,这种子类对象的初始化方式,称之为子类对象的隐式初始化
 *
 * 隐式初始化调用父类无参构造,无法给父类成员赋值,如果你有给父类成员赋值的需求,那就必须调用父类的有参构造
 * 语法:
 *      super(参数);
 * 而且这行super调用父类构造器的代码,必须显式的,直接的写在子类构造器的第一行.
 * 这种子类对象初始化的方式,称之为子类对象的显式初始化
 *
 * @since 17:40
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        Star s = new Star();
        Star s2 = new Star("🐴", "马童鞋");
        System.out.println(s2.stageName);
        System.out.println(s2.name);
    }
}

class Person {
    {
        System.out.println("Person的构造器执行了！");
    }

    /*static {
        System.out.println("Person类加载了");
    }*/
    String name = "周杰伦";

    public void eat() {
        System.out.println("我吃肉!");
    }

    public Person() {
        // super();
    }

    public Person(String name) {
        this.name = name;
    }
}

class Star extends Person {
    {
        System.out.println("Star的构造器执行了！");
    }
    /*static {
        System.out.println("Star类加载了");
    }*/

    String stageName = name + "同学";

    public void sing() {
        System.out.println("来一首爱的供养!");
    }

    public Star() {
        // super();
    }

    public Star(String stageName) {
        // super();
        this.stageName = stageName;
    }

    public Star(String stageName, String name) {
        super(name);
        this.stageName = stageName;
    }
}


/*class A{
    static {
        System.out.println("我是A的静态代码块！");
    }
}*/
